/*
 * @(#)AVReceive2.java	1.3 01/03/13
 *
 * Copyright (c) 1999-2001 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that i) this copyright notice and license appear on all copies of
 * the software; and ii) Licensee does not utilize the software in a manner
 * which is disparaging to Sun.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 *
 * This software is not designed or intended for use in on-line control of
 * aircraft, air traffic, aircraft navigation or aircraft communications; or in
 * the design, construction, operation or maintenance of any nuclear
 * facility. Licensee represents and warrants that it will not use or
 * redistribute the Software for such purposes.
 */

package javamail.rtp;

import java.io.IOException;
import javax.media.ConfigureCompleteEvent;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.DataSink;
import javax.media.Format;
import javax.media.Manager;
import javax.media.MediaLocator;
import javax.media.MediaTimeSetEvent;
import javax.media.NoProcessorException;
import javax.media.PrefetchCompleteEvent;
import javax.media.Processor;
import javax.media.RealizeCompleteEvent;
import javax.media.ResourceUnavailableEvent;
import javax.media.StopAtTimeEvent;
import javax.media.StopByRequestEvent;
import javax.media.Time;
import javax.media.rtp.*;
import javax.media.rtp.event.*;
import javax.media.protocol.DataSource;
import javax.media.control.BufferControl;
import javax.media.control.TrackControl;
import javax.media.datasink.DataSinkErrorEvent;
import javax.media.datasink.DataSinkEvent;
import javax.media.datasink.DataSinkListener;
import javax.media.datasink.EndOfStreamEvent;
import javax.media.format.AudioFormat;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.FileTypeDescriptor;


/**
 * AVReceive2 to receive RTP transmission using the new RTP API.
 */
public class rtpReceive implements ReceiveStreamListener, SessionListener,
        DataSinkListener, ControllerListener
{
    RTPManager mgr = null;
    DataSink dsink = null;
    MediaLocator ml = null;
    Processor processor = null;
    DataSource dataOutput = null;
    int duration = 60;
    boolean init = false;

    boolean dataReceived = false;
    Object dataSync = new Object();

   public rtpReceive(RTPManager mgr, AudioFormat[] fmt) {
       this.mgr = mgr;
    }

    protected boolean initialize() {
        // Set init as true
        init = true;
        try {
            mgr.addSessionListener(this);
            mgr.addReceiveStreamListener(this);
        } catch (Exception e){
            System.err.println("Cannot create the RTP Session: " + e.getMessage());
            return false;
        }
        return true;
    }

    public boolean waitForReceptionStart() {
	// Wait for data to arrive before moving on.
	long then = System.currentTimeMillis();
	long waitingPeriod = 5000;  // wait for a maximum of 5 secs.

	try{
            //System.err.println("rtpReceive:initialize()- Data Sync!");
            synchronized(dataSync) {
                while (init && !dataReceived &&
                    System.currentTimeMillis() - then < waitingPeriod) {
                    if (!dataReceived)
                        System.err.println("  - Waiting for RTP data to arrive...");
                    dataSync.wait(1000);
                }                //if((result=startTransmit())!=null) {
                //    System.err.println("Error occured while starting transmission: "+result);
                //}
            }
	} catch (Exception e) {}

	if (!dataReceived) {
	    System.err.println("No RTP data was received.");
	    close();
	    return false;
	}
        return true;
    }

    /**
     * Close the players and the session managers.
     */
    protected void close() {
        //System.err.println("rtpReceive:close()-Entering function");
        // Set init to false to stop writing to the file
        init = false;
	synchronized (this) {
            //System.err.println("rtpReceive:close()- INIT:"+init);
	    if (processor != null) {
		//processor.stop();
                //System.err.println("rtpReceive:close()-After processor.stop()");
                processor.close();
                //System.err.println("rtpReceive:close()-After processor.close()");
            }
        }
        //System.err.println("rtpReceive:close()-Exiting function");
    }

    /**
     * SessionListener.
     */
    public synchronized void update(SessionEvent evt) {
        //System.err.println("rtpReceive:update()- new Session event"+evt);
	if (evt instanceof NewParticipantEvent) {
	    Participant p = ((NewParticipantEvent)evt).getParticipant();
	    System.err.println("  - A new participant had just joined: " + p.getCNAME());
	}
    }


    /**
     * ReceiveStreamListener
     */
    public synchronized void update( ReceiveStreamEvent evt) {
        //System.err.println("rtpReceive:update()-ReceiveStreamEvent "+evt);

	RTPManager mgrSource = (RTPManager)evt.getSource();
	Participant participant = evt.getParticipant();	// could be null.
	ReceiveStream stream = evt.getReceiveStream();  // could be null.

	if (evt instanceof RemotePayloadChangeEvent) {
	    System.err.println("  - Received an RTP PayloadChangeEvent.");
	    System.err.println("Sorry, cannot handle payload change.");
            close();
	}
    
	else if (evt instanceof NewReceiveStreamEvent) {

	    try {
		stream = ((NewReceiveStreamEvent)evt).getReceiveStream();
		DataSource ds = stream.getDataSource();

		// Find out the formats.
		RTPControl ctl = (RTPControl)ds.getControl("javax.media.rtp.RTPControl");
		if (ctl != null){
		    System.err.println("  - Recevied new RTP stream: " + ctl.getFormat());
		} else
		    System.err.println("  - Recevied new RTP stream");

		if (participant == null)
		    System.err.println("      The sender of this stream had yet to be identified.");
		else {
		    System.err.println("      The stream comes from: " + participant.getCNAME()); 
		}

                // Create the processor and create datasink to write to file
                //createProcessor(ds, duration);
                dataOutput = ds;

      		// Notify intialize() that a new stream had arrived.
		synchronized (dataSync) {
                    //System.err.println("rtpReceive:update()- Data Received is true now");
		    dataReceived = true;
		    dataSync.notifyAll();
		}
	    } catch (Exception e) {
		System.err.println("NewReceiveStreamEvent exception " + e.getMessage());
		return;
	    }
        
	}

	else if (evt instanceof StreamMappedEvent) {

	     if (stream != null && stream.getDataSource() != null) {
		DataSource ds = stream.getDataSource();
		// Find out the formats.
		RTPControl ctl = (RTPControl)ds.getControl("javax.media.rtp.RTPControl");
		System.err.println("  - The previously unidentified stream ");
		if (ctl != null)
		    System.err.println("      " + ctl.getFormat());
		System.err.println("      had now been identified as sent by: " + participant.getCNAME());
	     }
	}

	else if (evt instanceof ByeEvent) {

	     System.err.println("  - Got \"bye\" from: " + participant.getCNAME());
	}
    }

    Object waitFileSync = new Object();
    boolean fileDone = false;
    boolean fileSuccess = true;

    /**
     * Block until file writing is done.
     */
    boolean waitForFileDone() {
	System.err.print("  ");
	synchronized (waitFileSync) {
	    try {
		while (!fileDone && init) {
		    if(processor.getMediaTime().getSeconds() > duration) {
			processor.close();
                    }
		    waitFileSync.wait(1000);
                    System.err.println("rtpReceive:waitForFileDone()-time: "+processor.getMediaTime().getSeconds());
		    System.err.print(".");
		}
	    } catch (Exception e) {}
	}
	System.err.println("");
	return fileSuccess;
    }

    /**
     * Event handler for the file writer.
     */
    public void dataSinkUpdate(DataSinkEvent evt) {
        System.err.println("rtpReceive.java:dataSinkUpdate()-Event: "+evt);
	if (evt instanceof EndOfStreamEvent) {
            // Cleanup.
            fileDone = true;
            dsink.close();
            //stopRecording();
            System.err.println("rtpReceive:dataSinkUpdate()-Writing file completed!");
	} else if (evt instanceof DataSinkErrorEvent) {
            // Cleanup.
            fileDone = true;
            fileSuccess = false;
            dsink.close();
            //stopRecording();
            System.err.println("rtpReceive:dataSinkUpdate()-Writing file stopped!");
	}
    }


    DataSink createDataSink(DataSource ds, MediaLocator ml) {
        DataSink datasink = null;
        try {
            datasink = Manager.createDataSink(ds, ml);
            datasink.open();
        } catch (Exception e) {
            System.err.println("createDataSink()-"+e.getMessage());
        }

        return datasink;
    }

     /****************************************************************
     * Convenience methods to handle processor's state changes.
     ****************************************************************/

    private Integer stateLock = new Integer(0);
    private boolean failed = false;

    Integer getStateLock() {
	return stateLock;
    }

    void setFailed() {
	failed = true;
    }

    private String createProcessor(DataSource ds, int duration) {
	// Try to create a processor to handle the input media locator
	try {
	    processor = javax.media.Manager.createProcessor(ds);
	} catch (NoProcessorException npe) {
	    return "Couldn't create processor";
	} catch (IOException ioe) {
	    return "IOException creating processor";
	}

        processor.addControllerListener(this);

	// Put the Processor into configured state.
	processor.configure();

	return null;
    }

     /**
     * Set the target transcode format on the processor.
     */
    boolean setTrackFormats(Processor p) {

	Format supported[];
        AudioFormat pcm44signed = new AudioFormat(javax.media.format.AudioFormat.LINEAR,44100,16,1);
        AudioFormat pcmaudio = new AudioFormat(javax.media.format.AudioFormat.LINEAR, 8000, 16, 1,AudioFormat.LITTLE_ENDIAN, AudioFormat.SIGNED);

	TrackControl [] tracks = p.getTrackControls();

	// Do we have at least one track?
	if (tracks == null || tracks.length < 1) {
	    System.err.println("Couldn't find tracks in processor");
	    return false;
	}

	for (int i = 0; i < tracks.length; i++) {
	    if (tracks[i].isEnabled()) {
		supported = tracks[i].getSupportedFormats();
		if (supported.length > 0) {
		    //tracks[i].setFormat(supported[0]);
                    //tracks[i].setFormat(pcm44signed);
                    tracks[i].setFormat(pcmaudio);
		} else {
		    System.err.println("Cannot transcode track [" + i + "]");
		    tracks[i].setEnabled(false);
		    return false;
		}
	    } else {
		tracks[i].setEnabled(false);
		return false;
	    }
	}
	return true;
    }

     /**
     * Set the content descriptor based on the given output MediaLocator.
     */
     void setContentDescriptor(Processor p, MediaLocator outML) {

 	ContentDescriptor cd;

	// If the output file maps to a content type,
	// we'll try to set it on the processor.

	if ((cd = fileExtToCD(outML.getRemainder())) != null) {

	    System.err.println("- set content descriptor to: " + cd);

	    if ((p.setContentDescriptor(cd)) == null) {

		// The processor does not support the output content
		// type.  But we can set the content type to RAW and
		// see if any DataSink supports it.

		p.setContentDescriptor(new ContentDescriptor(ContentDescriptor.RAW));
	    }
	}
     }

     /**
     * Convert a file name to a content type.  The extension is parsed
     * to determine the content type.
     */
     ContentDescriptor fileExtToCD(String name) {

	String ext;
	int p;

	// Extract the file extension.
	if ((p = name.lastIndexOf('.')) < 0)
	    return null;

	ext = (name.substring(p + 1)).toLowerCase();

	String type;

	// Use the MimeManager to get the mime type from the file extension.
	if ( ext.equals("mp3")) {
	    type = FileTypeDescriptor.MPEG_AUDIO;
	} else {
	    if ((type = com.sun.media.MimeManager.getMimeType(ext)) == null)
		return null;
	    type = ContentDescriptor.mimeTypeToPackageName(type);
	}

	return new FileTypeDescriptor(type);
     }

    Object waitSync = new Object();
    boolean stateTransitionOK = true;

    /**
     * Block until the processor has transitioned to the given state.
     * Return false if the transition failed.
     */
    boolean waitForState(Processor p, int state) {
	synchronized (waitSync) {
	    try {
		while (p.getState() < state && stateTransitionOK)
		    waitSync.wait();
	    } catch (Exception e) {}
	}
	return stateTransitionOK;
    }


    /**
     * Controller Listener.
     */
    public void controllerUpdate(ControllerEvent evt) {
        //System.err.println("rtpReceive:controllerUpdate()- Received event: "+evt);
	if (evt instanceof ConfigureCompleteEvent) {
            // Set the output content descriptor based on the media locator.
            setContentDescriptor(processor, ml);
            // Program the tracks.
            setTrackFormats(processor);
            // We are done with programming the processor.  Let's just
            // realize it.
            processor.realize();
	} else if (evt instanceof RealizeCompleteEvent) {
            processor.start();
            // Now, we'll need to create a DataSink.
            if ((dsink = createDataSink(processor.getDataOutput(), ml)) != null) {
                dsink.addDataSinkListener(this);
                fileDone = false;
                try { dsink.start();} catch (IOException e) {}
          	// Set the stop time if there's one set.
        	//if (duration > 0)
                //    processor.setStopTime(new Time((double)duration+2));
            }
	} else if (evt instanceof PrefetchCompleteEvent) {
	    synchronized (waitSync) {
		stateTransitionOK = false;
		waitSync.notifyAll();
	    }
	} else if (evt instanceof StopByRequestEvent) {
            if(processor!=null) {
                processor.removeControllerListener(this);
                processor.close();
                processor = null;
            }
            //System.err.println("rtpReceive:update()-Completed stop!");
	} else if (evt instanceof ResourceUnavailableEvent) {
	    synchronized (waitSync) {
		stateTransitionOK = false;
		waitSync.notifyAll();
	    }
	} else if (evt instanceof MediaTimeSetEvent) {
	    System.err.println("- mediaTime set: " +
		((MediaTimeSetEvent)evt).getMediaTime().getSeconds());
	} else if (evt instanceof StopAtTimeEvent) {
	    System.err.println("- stop at time: " +
		((StopAtTimeEvent)evt).getMediaTime().getSeconds());
	    evt.getSourceController().close();
	}
    }

    public void setLocator(MediaLocator ml) {
        this.ml = ml;
    }

    public void startRecording() {
      	System.err.println("rtpReceive:startRecording()-In function...");
        // OK, we can now start the actual transcoding.
        try {
            createProcessor(dataOutput, duration);
        } catch (Exception e) {}
    }

    public void stopRecording() {
        System.err.println("rtpReceive:stopRecording()-In function....");
        try {
            //dsink.stop();
            //dsink.close();
            processor.stop();
            //processor.close();
        } catch (Exception e) {}
    }

}// end of AVReceive2 